package eval.deploy;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import cern.jet.random.AbstractDistribution;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import eval.utilities.configuration.ExperimentConfiguration;
import eval.utilities.random.AlternatingDistribution;
import eval.vm.IVMManager;
import eval.vm.VMConfiguration;
import eval.vm.VMIdentifier;
import eval.vm.VirtualMachine;

/**
 *	This class hold the demand of all the VMs at any tick.
 *
 * 
 * @author Amit Peled   <amit.kapina@gmail.com>
 * @author Igal Kaufman <igal.kaufman@gmail.com>
 *
 */

@Singleton
public class DemandManager implements IDemandManager {
	
	private ExperimentConfiguration configuration = ExperimentConfiguration.getSingleton();
	private IVMManager vmManager;
	// maps virtual machines with their demand distribution
	private Map<VMIdentifier, AbstractDistribution> CPUdemandDistributionMap = new HashMap<VMIdentifier, AbstractDistribution>();
	// maps virtual machines with their CPU demand 
	private Map<VMIdentifier, VMRealDemand> demandTraces = new HashMap<VMIdentifier, VMRealDemand>();
	//maps virtual machines with their MEM demand 
	private Map<VMIdentifier, Integer> MEMdemandDistributionMap = new HashMap<VMIdentifier, Integer>();
	//to any tick at the arrays, we put a map of the live VMs with their demand 
	@SuppressWarnings("rawtypes")
	private Map[] cpuDemandByTick = new HashMap[configuration.experimentTicks];
	@SuppressWarnings("rawtypes")
	private Map[] memDemandByTick = new HashMap[configuration.experimentTicks];
	//Number of ticks that VMs are at low period
	private int lowPeriodAverageDuration;
	//Number of ticks that VMs are at high period
	private int highPeriodAverageDuration;
	
	/**
	 * @Constructor
	 * @param vmManager VMs manager
	 */
	@Inject
	public DemandManager(IVMManager vmManager) {
		this.vmManager = vmManager;
		this.lowPeriodAverageDuration = configuration.lowPeriodAverageDuration;
		this.highPeriodAverageDuration = configuration.highPeriodAverageDuration;
		for(int i=0; i<configuration.experimentTicks;i++) {
			cpuDemandByTick[i] = new HashMap<VirtualMachine, Integer>();
			memDemandByTick[i] = new HashMap<VirtualMachine, Integer>();
		}
	}
	
	/* (non-Javadoc)
	 * @see eval.deploy.IDemandManager#genrateDemand(java.util.List, int)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void genrateDemand(List<VirtualMachine> vmList, int tick) {
		VMIdentifier vmId;
		for(VirtualMachine vm : vmList) {
			vmId = vm.getIdentifier();
			for(int lifeTime=0;lifeTime<vm.getLifeTime();lifeTime++) {
				if(tick+lifeTime<configuration.experimentTicks) {
					cpuDemandByTick[tick+lifeTime].put(vm, new Integer(getCpuDemandOfVmAtTick(vmId, tick+lifeTime)));
					memDemandByTick[tick+lifeTime].put(vm, new Integer(getMEMDemandOfVmAtTick(vmId, tick+lifeTime)));
				}
			}
		}
	}
	
	/* (non-Javadoc)
	 * @see eval.deploy.IDemandManager#getMemDemandOfAllVMsAtTick(int)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public Map<VirtualMachine, Integer> getMemDemandOfAllVMsAtTick(int tick) {
		return memDemandByTick[tick];
	}

	/* (non-Javadoc)
	 * @see eval.deploy.IDemandManager#getCpuDemandOfAllVMsAtTick(int)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public Map<VirtualMachine, Integer> getCpuDemandOfAllVMsAtTick(int tick) {
		return cpuDemandByTick[tick];
	}
	
	
	/* (non-Javadoc)
	 * @see eval.deploy.IDemandManager#getCPUDemandOfVmAtTick(eval.vm.VirtualMachine, int)
	 */
	@Override
	public int getCPUDemandOfVmAtTick(VirtualMachine virtualMachine, int tick) {
		return getCpuDemandOfVmAtTick(virtualMachine.getIdentifier(),tick);
	}

	/* (non-Javadoc)
	 * @see eval.deploy.IDemandManager#getCpuDemandOfVmAtTick(eval.vm.VMIdentifier, int)
	 */
	@Override
	public int getCpuDemandOfVmAtTick(VMIdentifier virtualMachineID, int tick) {
		return getRealDemand(virtualMachineID,tick).getCPUDemandAtTick(tick);
	}
	
	/* (non-Javadoc)
	 * @see eval.deploy.IDemandManager#getMemDemandOfVmAtTick(eval.vm.VirtualMachine, int)
	 */
	@Override
	public int getMemDemandOfVmAtTick(VirtualMachine virtualMachine, int tick) {
		return getMEMDemandOfVmAtTick(virtualMachine.getIdentifier(),tick);
	}

	/* (non-Javadoc)
	 * @see eval.deploy.IDemandManager#getMEMDemandOfVmAtTick(eval.vm.VMIdentifier, int)
	 */
	@Override
	public int getMEMDemandOfVmAtTick(VMIdentifier virtualMachineID, int tick) {
		return getRealDemand(virtualMachineID,tick).getMEMDemandAtTick(tick);
	}
	
	/* (non-Javadoc)
	 * @see eval.deploy.IDemandManager#getRealDemand(eval.vm.VirtualMachine, int)
	 */
	@Override
	public VMRealDemand getRealDemand(VirtualMachine virtualMachine,int tick) {
		return getRealDemand(virtualMachine.getIdentifier(),tick);
	}
	
	/* (non-Javadoc)
	 * @see eval.deploy.IDemandManager#getRealDemand(eval.vm.VMIdentifier, int)
	 */
	@Override
	public VMRealDemand getRealDemand(VMIdentifier virtualMachine, int tick) {
		VMRealDemand demandTrace = demandTraces.get(virtualMachine);
		int lifeTime = vmManager.getVMbyID(virtualMachine).getLifeTime();
		if (demandTrace == null) {
			// demand didn't exists, create a new one
			AbstractDistribution demandDistribution = getDemandDistribution(virtualMachine);
			int memDemand = (vmManager.getVMConfiguration(virtualMachine).getMinMem() +
					vmManager.getVMConfiguration(virtualMachine).getMaxMem() )/2;
			MEMdemandDistributionMap.put(virtualMachine, memDemand);
			demandTrace = new VMRealDemand(demandDistribution,memDemand,
					vmManager.getVMConfiguration(virtualMachine),tick,lifeTime);
			demandTraces.put(virtualMachine, demandTrace);
		}
		return demandTrace;
	}
			
	/*
	 *  If distribution null create new one. 
	 * @param virtualMachine the VM id
	 * @return AbstractDistribution 
	 */
	private AbstractDistribution getDemandDistribution(VMIdentifier virtualMachine) {
		AbstractDistribution demandDistribution = CPUdemandDistributionMap.get(virtualMachine);
		if (demandDistribution == null) {
			// create distribution if it doesn't exist
			VMConfiguration vmConfiguration = vmManager.getVMConfiguration(virtualMachine);
			demandDistribution = generateNewDistribution(vmConfiguration.getMinCpu(), vmConfiguration.getMaxCpu());
			CPUdemandDistributionMap.put(virtualMachine, demandDistribution);
		}
		return demandDistribution;
	}
	
	/*
	 * Create new Distribution
	 * @param lowPeriodAverageDemand
	 * @param highPeriodAverageDemand
	 * @return
	 */
	private AbstractDistribution generateNewDistribution(
			int lowPeriodAverageDemand, int highPeriodAverageDemand) {
		// the specific distribution can be changed in the future in this method
		// do it parametric
		return new AlternatingDistribution(lowPeriodAverageDuration,
				lowPeriodAverageDemand, highPeriodAverageDuration,
				highPeriodAverageDemand);
	}

}
